package plp.funcoo.util;

import plp.funcoo.dec.classes.Declaration;
import plp.funcoo.environment.compilation.CompilationEnvironment;
import plp.funcoo.environment.execution.ExecutionEnvironment;
import plp.funcoo.exceptions.ClassAlreadyDeclared;
import plp.funcoo.exceptions.ClassNotDeclared;
import plp.funcoo.exceptions.MethodAlreadDeclared;
import plp.funcoo.exceptions.MethodNotDeclared;
import plp.funcoo.exceptions.ParameterNumberIncorrect;
import plp.funcoo.exceptions.ParameterTypeIncorrect;
import plp.funcoo.exceptions.VarAlreadyDeclared;
import plp.funcoo.exceptions.VarNotDeclared;
import plp.funcoo.expressions.Expression;


public class List<T> {

	protected T head;
    protected List<T> tail;
    
    public List(){
        head = null;
		tail = null;
    }
    
    public List(T value,  List<T> list){
        this.head = value;
        this.tail = list;
    }
    
    
    public void add(T value)
    {
    	
    	if(this.tail == null)
    		this.tail = new List<T>();	
    	if(this.head !=null)
    		this.tail.add(this.head);
    	this.head = value;
    }
    
    public int length() {
       
        if ( head  == null) {
            return  0;
        }else if (tail == null) {
            return 1;
        } else {
            return 1 + tail.length();
        }
        
    }
    
    public  T getHead() { 
        return head;
    }
    
    public List<T> getTail() {  
        return tail;
    }
    public T getNext()
    {
    	T aux = this.head;
    	
    	if(this.tail != null)
    	{
    		this.head = this.tail.getHead();
    		this.tail = this.tail.getTail();
    	}
    	
    	return aux;
    }
    
    public T getLast()
    {
    	T aux;
    	
    	if(this.head != null && (tail == null || this.tail.length()==0))
    	{
    		aux = this.head;
    		this.head = null;
    		return aux;
    	}
    	if((this.tail == null || this.tail.length()==0) && this.head == null)
    	{
    		return null;
    	}
    	else
    	{
    		//this.head = this.tail.getHead();
    		//this.tail = this.tail.getTail();
    		aux = this.tail.getLast();
    		return aux;
    	}
    	
    	
    }
    
    public String toString(){
        StringBuffer response = new StringBuffer();
        response = this.getString(response);
        return response.toString();
    }
    
    private StringBuffer getString(StringBuffer response){ 
        if(head != null) {
            if ( tail != null) {
                response = tail.getString(response);
            }
            response.append(head.toString()+" ");
        }
        return response;
    }
    
    public boolean typeCheck(CompilationEnvironment envi) throws VarAlreadyDeclared, VarNotDeclared, ClassAlreadyDeclared, ClassNotDeclared, MethodAlreadDeclared, MethodNotDeclared, ParameterTypeIncorrect, ParameterNumberIncorrect
    {
    	boolean result = true;
    	
    	if(length() >= 1)
    	{
    		if(length() > 1)
    			result = result && this.tail.typeCheck(envi);
    		if(this.head instanceof Expression)
    			result = result && ((Expression) this.head).typeCheck(envi);
    		else
    			result = result && ((Declaration) this.head).typeCheck(envi);
    	}
    	
    	
    	return result;
    }
    
    
    public ExecutionEnvironment evaluate(ExecutionEnvironment envi) throws VarNotDeclared, MethodNotDeclared, VarAlreadyDeclared, MethodAlreadDeclared 
    {
    	ExecutionEnvironment result = envi;
    	
    	if(length() >= 1)
    	{
    		if(length() > 1)
    			result =  this.tail.evaluate(result);
    		if(this.head instanceof Expression)
    			result = ((Expression) this.head).evaluate(result);
    		else
    			result = ((Declaration) this.head).evaluate(result);
    	}
    	
    	
    	return result;
    }
    
    
    public List<T> makeCopy()
    {
    	T aux = null;
    	if(this.head != null)
    	{
    		if(this.head instanceof Expression)
    			aux = (T)((Expression) this.head).makeCopy();
    		else
    			aux = (T)((Declaration) this.head).makeCopy();
    		
    	}

    	if(this.tail != null)
    		return new List<T>(aux, tail.makeCopy());
    	else
    		return new List<T>(aux, this.tail);
    		
    }
    
    
}
